package actor

import (
	"fmt"
	"log"
	"sync"
	"time"

	aactor "github.com/asynkron/protoactor-go/actor"

	sadefine "gitee.com/simplexyz/simplego/actor/define"
	satimer "gitee.com/simplexyz/simplego/actor/timer"
	sawork "gitee.com/simplexyz/simplego/actor/work"
	slog "gitee.com/simplexyz/simplego/log"
)

func Init(rootName string, deciderFunc sadefine.DeciderFunc, options ...sadefine.ConfigOption) {
	gSystem = aactor.NewActorSystem(options...)

	gRootStartedWg = &sync.WaitGroup{}
	gRootStoppedWg = &sync.WaitGroup{}

	if deciderFunc == nil {
		log.Panicf("deciderFunc is nil")
	}

	props := aactor.PropsFromProducer(func() aactor.Actor {
		return createRoot(gRootStartedWg, gRootStoppedWg)
	}, aactor.WithSupervisor(aactor.NewOneForOneStrategy(0, 0, deciderFunc)))

	gRootStartedWg.Add(1)

	var err error
	gRootPID, err = RootContext().SpawnNamed(props, rootName)
	if err != nil {
		log.Panicf("spawn root fail, %v", err)
	}

	gRootStartedWg.Wait()
}

func Final() {
	Poison(gRootPID)
	gRootStoppedWg.Wait()
}

//func System() *sadefine.ActorSystem {
//	return gSystem
//}

func RootContext() *sadefine.RootContext {
	return gSystem.Root
}

func RootPID() *sadefine.PID {
	return gRootPID
}

func Spawn(ctx sadefine.Context, fromRoot bool, name string, wait bool, optionFuncs ...OptionFunc) (*sadefine.PID, error) {
	opt := createOption()

	for _, f := range optionFuncs {
		f(opt)
	}

	if opt.name == "" && name != "" {
		opt.name = name
	}

	a := create(opt)

	var propsOptions []sadefine.PropsOption

	if opt.mailBoxSize > 0 {
		propsOptions = append(propsOptions, aactor.WithMailbox(aactor.Bounded(opt.mailBoxSize)))
	} else {
		propsOptions = append(propsOptions, aactor.WithMailbox(aactor.Unbounded()))
	}

	if opt.decider != nil {
		propsOptions = append(propsOptions, aactor.WithSupervisor(aactor.NewOneForOneStrategy(0, 0, opt.decider)))
	}

	props := aactor.PropsFromProducer(func() aactor.Actor { return a }, propsOptions...)

	var pid *sadefine.PID
	var err error

	if ctx != nil {
		if name != "" {
			pid, err = ctx.SpawnNamed(props, name)
			if err != nil {
				return nil, fmt.Errorf("ctx.SpawnNamed fail, %w", err)
			}
		} else {
			pid = ctx.Spawn(props)
		}
	} else {
		if fromRoot {
			err = sawork.Dispatch(RootContext(), RootPID(), 0, sawork.DispatchFunc(func(ctx sadefine.Context) error {
				if name != "" {
					pid, err = ctx.SpawnNamed(props, name)
					if err != nil {
						return fmt.Errorf("gRootPID ctx.SpawnNamed fail, %w", err)
					}
				} else {
					pid = ctx.Spawn(props)
				}
				return nil
			}))
		} else {
			pid, err = RootContext().SpawnNamed(props, name)
			if err != nil {
				return nil, fmt.Errorf("RootContext().SpawnNamed fail, %w", err)
			}
		}
	}

	if err == nil && wait {
		a.startedWg.Wait()
	}

	return pid, err
}

func Stop(pid *sadefine.PID) {
	RootContext().Stop(pid)
}

func Poison(pid *sadefine.PID) {
	RootContext().Poison(pid)
}

func Send(pid *sadefine.PID, message any) {
	RootContext().Send(pid, message)
}

func RequestFuture(pid *sadefine.PID, message any, timeout time.Duration) *sadefine.Future {
	return RootContext().RequestFuture(pid, message, timeout)
}

func GetLogger(ctx sadefine.Context) slog.ILogger {
	a := ctx.Actor().(*Actor)
	return a.logger
}

func GetTimerManager(ctx sadefine.Context) *satimer.Manager {
	a := ctx.Actor().(*Actor)
	return a.timerMgr
}
